import java.util.Random;


public class Vector {

	private double[] p;
	
	public Vector(){
		this.p = new double[3];
		for(int i=0;i<3;i++) this.p[i] = 0.0;
		
	}
	
	public Vector(double p0, double p1, double p2){
		this();
		this.p[0] = p0;
		this.p[1] = p1;
		this.p[2] = p2;
	}
	
	public Vector(double[] args){
		this(args[0], args[1], args[2]);
	}
	
	public double[] getCoords(){
		return this.p;
	}
	
	public static Vector crossProduct( Vector v1, Vector v2){
		double[] c1 = v1.getCoords();
		double[] c2 = v2.getCoords();
		return new Vector(c1[1]*c2[2] - c1[2]*c2[1], - (c1[0]*c2[2] - c1[2]*c2[0]) , c1[0]*c2[1] - c1[1]*c2[0]);
	}
	
	public static Vector crossProduct_n( Vector v1, Vector v2){
		return crossProduct(v1, v2).normlized();
	}
	
	public static double dotProduct( Vector v1, Vector v2){
		double[] c1 = v1.getCoords();
		double[] c2 = v2.getCoords();
		return c1[0]*c2[0] + c1[1]*c2[1] + c1[2]*c2[2] ;
	}
	
	public static Vector sub(Vector v1, Vector v2){
		double[] c1 = v1.getCoords();
		double[] c2 = v2.getCoords();
		return new Vector(c1[0]-c2[0] , c1[1] - c2[1] , c1[2] - c2[2]);	
	}
	
	public static Vector sub_n(Vector v1, Vector v2){
		return sub(v1, v2).normlized();
	}
	
	public static Vector add(Vector v1, Vector v2){
		double[] c1 = v1.getCoords();
		double[] c2 = v2.getCoords();
		return new Vector(c1[0] + c2[0] , c1[1] + c2[1] , c1[2] + c2[2]);	
	}
	
	public Vector normlized(){
		double size = this.getSize();
		if (size == 0.0) return new Vector();
		return new Vector(this.p[0]/size , this.p[1]/size, this.p[2]/size);
	}
	
	public boolean isEqual_n(Vector v){
		Vector v1 = this.normlized();
		Vector v2 = v.normlized();
		return v1.isEqual(v2);
	}
	
	private boolean isEqual(Vector v){
		Vector diff = Vector.sub(this, v);
		double size = diff.getSize();
		double accuracy = 1E-4;
		return (size < accuracy);
	}
	
	public double getSize(){
		return Math.sqrt(this.getSize_Q());
	}
	
	public double getSize_Q(){
		return this.p[0]*this.p[0] + this.p[1]*this.p[1] + this.p[2]*this.p[2];
	}
	
	public Vector mul(double factor){
		return new Vector(factor*p[0] ,factor*p[1] ,factor*p[2] );
	}
	
	public static boolean isInAngularRange(Vector origin, Vector bound1, Vector bound2, Vector point ){
		
		Vector cross1 = Vector.crossProduct_n(Vector.sub_n(bound1, origin), Vector.sub_n(point, origin));
		Vector cross2 = Vector.crossProduct_n(Vector.sub_n(point, origin), Vector.sub_n(bound2, origin));
		Vector cross3 = Vector.crossProduct_n(Vector.sub_n(bound1, origin), Vector.sub_n(bound2, origin));
		
		return ( cross1.isEqual_n(cross2) && cross1.isEqual_n(cross3));	
	}
	
	public String print(){
		return "( " + p[0] + ", " + p[1] + ", " + p[2] + ")";
	}
	
	public static double getDistance(Vector v1, Vector v2){
		return Vector.sub(v1, v2).getSize();
	}
	
	public static double getDistance_Q(Vector v1, Vector v2){
		return Vector.sub(v1, v2).getSize_Q();
	}
	
	public static double pointToLineDistance(Vector point, Vector source, Vector target){
		Vector direction = Vector.sub_n(target, source);
		double var = dotProduct(sub(point, source), direction) / 
						dotProduct(direction, direction);
		Vector closestPoint = add(source, direction.mul(var));
		return getDistance(closestPoint, point);	
	}
	
	public static double pointToLineDistance_Q(Vector point, Vector base, Vector direction){
		double var = dotProduct(sub(point, base), direction) / 
						dotProduct(direction, direction);
		Vector closestPoint = add(base, direction.mul(var));
		return getDistance_Q(closestPoint, point);	
	}
	
	public boolean isUnderPlain(Vector base, Vector direction){
		return ( dotProduct(sub(this, base), direction) < 0);						
	}

	public static double cosAngle(Vector v1, Vector v2){
		return Vector.dotProduct(v1.normlized(), v2.normlized());
	}
	/*
	public boolean isInPlain(Vector base, Vector normal){
		double sigma = 1E-2;
		double diff =  Math.abs(Vector.dotProduct(this, normal)- Vector.dotProduct(base, normal)); 
		return (diff < sigma);
	}
	*/
	
	public double distanceFromPlain(Vector base, Vector normal){
		return Math.abs(Vector.dotProduct(this, normal)- Vector.dotProduct(base, normal)); 
	}
	
	public static Vector getVector_sameAngle_samePlain(Vector N, Vector L){
		double ca = cosAngle(N,L);
		return Vector.add(L,Vector.sub(N.mul(ca), L).mul(2));	
	}
	
	public static boolean isHiding( Vector origin, Vector interruption, 
						Vector viewDirection, Vector lightDirection, Primitive p){
		
		if (origin.isEqual_n(interruption)){
			if (Vector.cosAngle(p.getNormalAt(origin, viewDirection)
					, lightDirection) < 0){
				return false;
			}
			return true;
		}
		double var = Vector.dotProduct(Vector.sub(interruption, origin),lightDirection.mul(-1))
						/ Vector.dotProduct(lightDirection, lightDirection);
		if (var < 0) {
			return false;
		}
		return true;
	}
	
	public static Vector generate_RandomVector_outOfPlain(Vector origin, Vector normal){
		double x,y,z;
		Random randomGenerator = new Random();
		Vector rand_vector;
		while(true){
			x = 1 - 2*randomGenerator.nextDouble();
			y = 1 - 2*randomGenerator.nextDouble();
			z = 1 - 2*randomGenerator.nextDouble();
			rand_vector = new Vector(x,y,z);
			if(rand_vector.isEqual_n(normal) || rand_vector.isEqual_n(normal.mul(-1))){
				continue;
			}
			if(Vector.dotProduct(rand_vector, normal) == 0){
				continue;
			}
			if(Vector.cosAngle(rand_vector, normal) < 0){
				rand_vector = rand_vector.mul(-1);
			}
			return rand_vector;
		}		
	}
}

